/*
 * Copyright (c) 2004, Steven Baldasty <sbaldasty@bitflippin.org>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * Contributors:
 *    Steven Baldasty <sbaldasty@bitflippin.org>
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 */

package org.bitflippin.ninjarobots;

import java.awt.BorderLayout;
import java.awt.FileDialog;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StreamTokenizer;
import java.io.StringReader;
import java.util.LinkedList;

import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.text.BadLocationException;

// Integrated development environments for scenarios and robot programs.
// Support load, save, and editing features.
// Handle tokenization but defer parsing to child classes.

public abstract class IDE extends CustomPanel implements ActionListener  {

	// Private components.
	private JTextArea inArea;
	private JTextArea outArea;
	private JButton loadButton = new JButton("Load");
	private JButton saveButton = new JButton("Save");
	private JButton cancelButton = new JButton("Cancel");
	private JButton parseButton = new JButton("Parse");

	// Token list for parse to use.
	// Generated by pressedParse.
	protected LinkedList tokens = new LinkedList();

	// Editor title.
	// Appears above input text area.
	abstract protected String header();

	// Name of file currently loaded.
	private String file;

	// Field on setup screen where active file appears.
	// Updated when parse completes successfully.
	private JTextField fileField;

	// Initialize components.
	// Let t goto fileField.
	public IDE(NinjaRobots f, JTextField t)  {
		super(new BorderLayout(), f);
		fileField = t;
		loadButton.addActionListener(this);
		saveButton.addActionListener(this);
		cancelButton.addActionListener(this);
		parseButton.addActionListener(this);
		CustomTextArea cti = new CustomTextArea(f, header(), 8);
		CustomTextArea cto = new CustomTextArea(f, "Parser output:", 2);
		inArea = cti.getArea();
		outArea = cto.getArea();
		outArea.setEditable(false);
		outArea.setLineWrap(true);
		outArea.setWrapStyleWord(true);
		addCtr(" ", BorderLayout.NORTH);
		JPanel p = new JPanel(new BorderLayout());
		p.add(cti, BorderLayout.NORTH);
		p.add(cto, BorderLayout.CENTER);
		add(p, BorderLayout.CENTER);
		JPanel q = new JPanel(new FlowLayout());
		q.add(loadButton);
		q.add(saveButton);
		q.add(cancelButton);
		q.add(parseButton);
		add(q, BorderLayout.SOUTH);
		clear();
		setVisible(false);
		frame.getContentPane().add(this);
	}

	// Button driven action dispatcher.
	// Defined by contract with ActionListener.
	public void actionPerformed(ActionEvent e)  {
		Object o = e.getSource();
		if(o == loadButton)
			pressedLoad();
		else if(o == saveButton)
			pressedSave();
		else if(o == cancelButton)
			pressedCancel();
		else if(o == parseButton)
			pressedParse();
	}

	// Allow user to choose file.
	// Dump file content into input area for editing.
	private void pressedLoad()  {
		FileDialog d = new FileDialog(frame, "Load", FileDialog.LOAD);
		d.setVisible(true);
		String s = d.getFile();
		if(s != null)
			try  {
				file = s;
				FileReader f = new FileReader(d.getDirectory() + s);
				inArea.read(f, null);
				f.close();
			}
			catch(IOException e)  {
				System.out.println("I/O Error");
				System.exit(0);
			}
		inArea.setTabSize(2);
	}

	// Allow user to choose file.
	// Dump input area to file.
	private void pressedSave()  {
		FileDialog d = new FileDialog(frame, "Save", FileDialog.SAVE);
		d.setVisible(true);
		String s = d.getFile();
		if(s != null)
			try  {
				file = s;
				FileWriter f = new FileWriter(d.getDirectory() + s);
				inArea.write(f);
				f.close();
			}
			catch(IOException e)  {
				System.out.println("I/O Error");
				System.exit(0);
			}
		inArea.setTabSize(2);
	}

	// Back to setup screen.
	// Keep current file open.
	private void pressedCancel()  {
		setVisible(false);
		frame.setupPanel.setVisible(true);
	}

	// Tokenize text in inputArea, ask child to finish parsing.
	// Report errors or accept and return to setup screen.
	private void pressedParse()  {
		outArea.setText("");
		fileField.setText("");
		StreamTokenizer st = new StreamTokenizer(new StringReader(inArea.getText()));
		st.resetSyntax();
		st.commentChar(';');
		st.eolIsSignificant(true);
		st.lowerCaseMode(false);
		st.quoteChar('"');
		st.slashSlashComments(false);
		st.slashStarComments(false);
		st.whitespaceChars(' ', ' ');
		st.whitespaceChars('\t', '\t');
		st.wordChars('A', 'Z');
		st.wordChars('a', 'z');
		st.wordChars('0', '9');
		st.wordChars('_', '_');
		Token.resetLineCounter();
		tokens.clear();
		try  {
			while(st.nextToken() != StreamTokenizer.TT_EOF)  {
				Token t = new Token(st);
				tokens.add(t);
				if(t.getType() == Token.INVALID)  {
					error("Invalid token: " + t.getContent(), t);
					badToken();
					return;
				}
			}
			tokens.add(new Token());
		}
		catch(IOException e)  {
			System.out.println("I/O Error");
			System.exit(0);
		}
		try  { parse(); }
		catch(InterpreterException e)  {
			error(e.getError(), e.getToken());
			return;
		}
		fileField.setText(file);
		setVisible(false);
		if(frame.setupPanel != null)
			frame.setupPanel.setVisible(true);
	}

	// Output error message m.
	// Highlight line with token t.
	private void error(String m, Token t)  {
		outArea.setText(m);
		try  {
			if(t == null)
				inArea.setCaretPosition(inArea.getText().length());
			else  {
				inArea.setCaretPosition(0);
				int line = t.getLine();
				int start = inArea.getLineStartOffset(line);
				int end = inArea.getLineEndOffset(line);
				inArea.select(start, end);
			}
		}
		catch(BadLocationException e)  {
			System.out.println("Internal error.");
			System.exit(0);
		}
		inArea.requestFocus();
	}

	// Complete parsing process.
	// Return any problematic token.
	abstract protected void parse() throws InterpreterException;

	// Called whenever tokenization failed.
	abstract protected void badToken();

	// Reset the whole IDE.
	public void clear()  {
		inArea.setText("");
		file = "Untitled";
		fileField.setText("");
		pressedParse();
	}

}
